/*
 * Alloy Analyzer
 * Copyright (c) 2002 Massachusetts Institute of Technology
 *
 * The Alloy Analyzer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The Alloy Analyzer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Alloy Analyzer; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package alloy.viz.alloyviz;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import alloy.ast.ASTDepthFirstVisitor;
import alloy.ast.ArrowMultExpr;
import alloy.ast.BinaryExpr;
import alloy.ast.BinaryExprOp;
import alloy.ast.Decl;
import alloy.ast.Expr;
import alloy.ast.Module;
import alloy.ast.ModuleInfo;
import alloy.ast.Modules;
import alloy.ast.MultiplicityExpr;
import alloy.ast.SetMultExpr;
import alloy.ast.SigExpr;
import alloy.ast.SigExprs;
import alloy.ast.Signature;
import alloy.ast.Specification;
import alloy.ast.Variable;
import alloy.ast.VariableExpr;
import alloy.type.RelationType;
import alloy.type.SigType;
import alloy.util.Dbg;
import alloy.viz.dotviz.DotColor;
import alloy.viz.dotviz.DotPalette;
import alloy.viz.dotviz.DotShape;
import alloy.viz.dotviz.DotStyle;

/**
 * Takes an AST, analyzes the sigs, and generates a metamodel using the alloyviz
 * classes and specs.
 */
public class MetamodelGenerator {

    static final String SIG_TYPE_NAME = "_meta Sig";
    static final String TYPE_TYPE_NAME = "_meta Type";
    static final String SET_TYPE_NAME = "_meta Set";
    static final String RELNAME_TYPE_NAME = "_meta Fieldname";
    static final String EXTENDS_REL_NAME = "_meta extends";
    static final String IN_REL_NAME = "_meta in";
    static final String FIELDS_REL_NAME = "_meta fields";

    static final String METAMODEL_NAME = "metamodel";
    static final String OBJECTMODEL_VIEW_NAME = "object model";

    private static ViewPalette _metaPal;

    private Specification _spec;

    /*
     * _fieldNameToTuples: AlloyRelation->Set(List[penultimate col name, last col name])
     * _xtendsSet: Lists of form typeName->superTypeName
     * _inSet: Lists of form setName->typeName
     */
    private Set _extendsSet, _inSet;
    private Map _fieldNameToTuples;

    private TypeStructure _ts;

    private AlloyModule _module;
    private List _modules;

    private Model _model;

    private VizInstance _instance;

    private static AlloyType _sig = new AlloyType(SIG_TYPE_NAME);
    private static AlloyType _type = new AlloyType(TYPE_TYPE_NAME);
    private static AlloyType _set = new AlloyType(SET_TYPE_NAME);
    private static AlloyType _relName = new AlloyType(RELNAME_TYPE_NAME);
    private AlloyRelation _xtends, _in;

    //private Map _arityToAlloyRel;

    private Map _sigNamesToAtoms;
    // map from a sig to its representative element

    private boolean _infoProcessed;

    public MetamodelGenerator(Specification spec_) {
        _infoProcessed = false;

        _spec = spec_;

        //_arityToAlloyRel = new HashMap();

        _sigNamesToAtoms = new HashMap();

        _fieldNameToTuples = new HashMap();
        _extendsSet = new HashSet();
        _inSet = new HashSet();

        _ts = new TypeStructure();

        _ts.addType(_sig);
        _ts.addSuper(_sig, _type);
        _ts.addSuper(_sig, _set);
        _ts.addType(_relName);

        _spec.applyVisitor(new ObjectModelVisitor());

        compileInfo();

        _infoProcessed = true;

    }

    private void compileInfo() {
        List sets = new ArrayList();

        List fieldsTypes = new ArrayList();
        fieldsTypes.add(_sig);
        fieldsTypes.add(_relName);
        fieldsTypes.add(_sig);

        List xtTypes = new ArrayList();
        xtTypes.add(_type);
        xtTypes.add(_type);
        _xtends = new AlloyRelation(EXTENDS_REL_NAME, xtTypes);

        List inTypes = new ArrayList();
        inTypes.add(_set);
        inTypes.add(_type);
        _in = new AlloyRelation(IN_REL_NAME, inTypes);

        List rels = new ArrayList();

        for (Iterator fieldIter = _fieldNameToTuples.keySet().iterator(); fieldIter.hasNext();) {
            rels.add((AlloyRelation)fieldIter.next());
        }

        rels.add(_xtends);
        rels.add(_in);

        _module = new AlloyModule(METAMODEL_NAME, _ts.getTypes(), sets, rels);
        _modules = new ArrayList();
        _modules.add(_module);

        _model = new Model(_modules, _ts, METAMODEL_NAME);

        // instance stuff now

        _instance = new VizInstance(_model);

        // adds each atom for each sig
        for (Iterator sigIter = _sigNamesToAtoms.keySet().iterator(); sigIter.hasNext();) {
            AlloyAtom sigAtom = (AlloyAtom)_sigNamesToAtoms.get(sigIter.next());
            Dbg.chk(sigAtom);
            _instance.addAtom(sigAtom);
        }

        // add "in" tuples to the "in" relation
        for (Iterator inIter = _inSet.iterator(); inIter.hasNext();) {
            List tup = (List)inIter.next();
            List alyTup = new ArrayList();
            for (Iterator tupIter = tup.iterator(); tupIter.hasNext();) {
                String name = (String)tupIter.next();
                AlloyAtom atom = (AlloyAtom)_sigNamesToAtoms.get(name);
                Dbg.chk(atom);
                alyTup.add(atom);
            }
            _instance.addTupleToRelation(new AlloyTuple(alyTup), _in);
        }

        //  add "extends" tuples to the "extends" relation
        for (Iterator extendsIter = _extendsSet.iterator(); extendsIter.hasNext();) {
            List tup = (List)extendsIter.next();
            List alyTup = new ArrayList();
            for (Iterator tupIter = tup.iterator(); tupIter.hasNext();) {
                String name = (String)tupIter.next();
                AlloyAtom atom = (AlloyAtom)_sigNamesToAtoms.get(name);
                Dbg.chk(atom);
                alyTup.add(atom);
            }
            _instance.addTupleToRelation(new AlloyTuple(alyTup), _xtends);
        }

        // the arrows for fields are binary rels of type sig->sig
        List fieldRelTypes = new ArrayList();
        fieldRelTypes.add(_sig);
        fieldRelTypes.add(_sig);

        // add fields
        for (Iterator fieldIter = _fieldNameToTuples.keySet().iterator(); fieldIter.hasNext();) {
            /* boolean that in the end dictates whether or the field is to be added
             used for safeguarding against Alloy 2.x's messy polymorphism--in
             otherwords it ignores polymorphic relations right now.
            
            boolean failed = false;
            List stringTuple = (List)fieldIter.next();
            List atomTuple = new ArrayList(stringTuple.size());
            
            for (Iterator atomIter = stringTuple.iterator(); atomIter.hasNext();) {
                String name = (String)atomIter.next();
                AlloyAtom next = (AlloyAtom)_sigNamesToAtoms.get(name);
                if (next == null) {
                    // couldn't find the atom, even though all sigs have
                    // been added. must imply this was a polymorphic sigexpr
                    // (actually maybe not...could have just been any type
                    // param--will fix *this* asap).
                    failed = true;
                    break;
                }
                atomTuple.add(next);
            }
            
            //System.out.println(stringTuple);
            
            if (!failed) {
                AlloyRelation fieldRel =
                    (AlloyRelation)_arityToAlloyRel.get(new Integer(atomTuple.size()));
                _instance.addTupleToRelation(new AlloyTuple(atomTuple), fieldRel);
            
            }*/

            AlloyRelation curField = (AlloyRelation)fieldIter.next();
            Set tupSet = (Set)_fieldNameToTuples.get(curField);

            for (Iterator tupIter = tupSet.iterator(); tupIter.hasNext();) {
                List curFieldInfo = (List)tupIter.next();
                List lastTwoColumns = new ArrayList();
                String atom1Name = (String)curFieldInfo.get(0);
                String atom2Name = (String)curFieldInfo.get(1);
                AlloyAtom atom1 = (AlloyAtom)_sigNamesToAtoms.get(atom1Name);
                AlloyAtom atom2 = (AlloyAtom)_sigNamesToAtoms.get(atom2Name);
		//System.out.println("curFieldInfo " + curFieldInfo);
		//System.out.println("atom1Name "+ atom1Name);
		//System.out.println("atom1 " + atom1);
		//System.out.println("atom2Name "+atom2Name);
		//System.out.println("atom2 " + atom2);
                Dbg.chk(atom1);
                Dbg.chk(atom2);

                lastTwoColumns.add(atom1);
                lastTwoColumns.add(atom2);
                AlloyTuple lastTwoTup = new AlloyTuple(lastTwoColumns);
                _instance.addTupleToRelation(lastTwoTup, curField);
            }

        }

    }

    /*private void generateFieldRel(int i) {
        if (_arityToAlloyRel.get(new Integer(i)) == null) {
            int j = i;
            List fieldsTypes = new ArrayList();
            fieldsTypes.add(_sig);
            fieldsTypes.add(_relName);
            while (j > 2) {
                fieldsTypes.add(_sig);
                j--;
            }
            AlloyRelation rel = new AlloyRelation(FIELDS_REL_NAME, fieldsTypes);
            _arityToAlloyRel.put(new Integer(i), rel);
        }
    }*/

    public Model getModel() {
        if (_infoProcessed) {
            return _model;
        }
        //System.out.println("model requested before info processed");
        return null;
    }

    public VizInstance getInstance() {
        if (_infoProcessed) {
            return _instance;
        }
        //System.out.println("instance requested before info processed");
        return null;
    }

    /**
     * Returns a ViewPalette that will match the given metamodel (i.e. it
     * fills up as many "fields" fields as it needs to.  It always returns
     * the same instance of ViewPalette, so changes will be reflected on
     * all viz's that called this method.
     *
     */
    public static ViewPalette getMetaPalette(Model model_) {
        if (_metaPal == null) {
            View newView = new View(OBJECTMODEL_VIEW_NAME);
            _metaPal = new ViewPalette(METAMODEL_NAME);

            VizMap vm = newView.getModelView().getVizMap();

            GeneralView gv = newView.getGeneralView();

            gv.setNodePalette(DotPalette.MARTHA);
            gv.setEdgePalette(DotPalette.MARTHA);

            NodeViz sigViz =
                new NodeViz(Boolean.TRUE, "", null, null, null, null, null, null, null, null, null);

            NodeViz typeViz =
                new NodeViz(
                    Boolean.TRUE,
                    "",
                    DotColor.MARTHA_RED,
                    DotShape.BOX,
                    DotStyle.SOLID,
                    Boolean.FALSE,
                    null,
                    Boolean.FALSE,
                    Boolean.FALSE,
                    Boolean.FALSE,
                    Boolean.FALSE);

            NodeViz setViz =
                new NodeViz(
                    Boolean.TRUE,
                    "",
                    DotColor.MARTHA_BLUE,
                    DotShape.ELLIPSE,
                    DotStyle.SOLID,
                    Boolean.FALSE,
                    null,
                    Boolean.FALSE,
                    Boolean.FALSE,
                    Boolean.FALSE,
                    Boolean.FALSE);

            NodeViz relNameViz =
                new NodeViz(
                    Boolean.FALSE,
                    "",
                    null,
                    null,
                    null,
                    null,
                    null,
                    Boolean.FALSE,
                    null,
                    null,
                    Boolean.FALSE);

            vm.addNodeMapping(_sig, sigViz);
            vm.addNodeMapping(_type, typeViz);
            vm.addNodeMapping(_set, setViz);
            vm.addNodeMapping(_relName, relNameViz);

            // now the the relations
            EdgeViz extendsViz =
                new EdgeViz(
                    Boolean.TRUE,
                    "extends",
                    DotColor.BLACK,
                    DotStyle.DOTTED,
                    100,
                    null,
                    null,
                    null,
                    Boolean.FALSE,
                    Boolean.TRUE);

            EdgeViz inViz =
                new EdgeViz(
                    Boolean.TRUE,
                    "in",
                    DotColor.BLACK,
                    DotStyle.DOTTED,
                    100,
                    null,
                    null,
                    null,
                    Boolean.FALSE,
                    Boolean.TRUE);

            EdgeViz fieldsViz =
                new EdgeViz(
                    Boolean.TRUE,
                    "",
                    DotColor.BLACK,
                    DotStyle.SOLID,
                    0,
                    null,
                    null,
                    null,
                    Boolean.FALSE,
                    null);

            for (Iterator relIter = model_.getRelations().iterator(); relIter.hasNext();) {
                AlloyRelation rel = (AlloyRelation)relIter.next();
                if (rel.getName().equals(EXTENDS_REL_NAME)) {
                    vm.addEdgeMapping(rel, extendsViz);

                } else if (rel.getName().equals(IN_REL_NAME)) {
                    vm.addEdgeMapping(rel, inViz);
                } else if (rel.getName().equals(FIELDS_REL_NAME)) {
                    vm.addEdgeMapping(rel, fieldsViz.copy());
                    // can have multiple ones

                }
            }

            _metaPal.addView(newView);
            _metaPal.setCurrentView(newView);

            return _metaPal;
        } else {

            VizMap vm = null;

            for (Iterator viewIter = _metaPal.getAllViews().iterator(); viewIter.hasNext();) {
                View nextView = (View)viewIter.next();
                if (nextView.getName().equals(OBJECTMODEL_VIEW_NAME)) {
                    vm = nextView.getModelView().getVizMap();
                    break;
                }
            }

            if (vm == null) {
                // this was a corrupted metamodel palette, createa new one
                _metaPal = null;
                return getMetaPalette(model_);
            }

            EdgeViz fieldsViz =
                new EdgeViz(
                    Boolean.TRUE,
                    "",
                    DotColor.BLACK,
                    DotStyle.SOLID,
                    0,
                    null,
                    null,
                    null,
                    Boolean.FALSE,
                    null);

            for (Iterator relIter = model_.getRelations().iterator(); relIter.hasNext();) {
                AlloyRelation rel = (AlloyRelation)relIter.next();
                if (rel.getName().equals(FIELDS_REL_NAME)) {
                    vm.addEdgeMapping(rel, fieldsViz.copy());
                    // can have multiple ones

                }
            }

            return _metaPal;
        }
    }

    /**
     * inner class used to traverse the AST and get field information
     *
     */
    class ObjectModelVisitor extends ASTDepthFirstVisitor {

        ModuleInfo curMod;
        Signature curSig;
        String curSigName;
        //String curFieldName;
        Set curSetOfCols;
        List curColumns;
        int curColIndex;
        //boolean multiplyOutCurCols;

        public void visit(Specification spec_) {
            Modules mods = spec_.getModules();
            mods.applyVisitor(this);
        }

        public void visit(Module module_) {
            Module curMod = module_;
            curMod.getSignatures().applyVisitor(this);
        }

        public void visit(SigExpr sigExpr_) {
            List col;
            if (curColumns.size() <= curColIndex) {
                col = new ArrayList();
                curColumns.add(curColIndex, col);
            } else {
                col = (List)curColumns.get(curColIndex);
            }
            col.add(sigExpr_.getSig().getId().nodeString());
        }

        public void visit(VariableExpr varExpr_) {
	    /** NOTE by Emina:  This is necessary to handle declarations of the form
	     * sig A {foo : this } 
	     * If I understand the rest of the code correctly, then this method should
	     * be reached iff a field declaration contains the keyword "this" --
	     * hence the Dbg.chk statement below
	     **/
	    Dbg.chk(varExpr_.getVar().getId().nodeString().equals("this"));
	    curSig.toSigExpr().applyVisitor(this);
            /* if a field is found, add the type; e.g.,
             * sig A {faa: A, foo: faa }
             * draws edge from A to A (type of this.faa)
            
            // System.out.println("VarExpr visited for metamodel");
            List col;
            if (curColumns.size() <= curColIndex) {
                col = new ArrayList();
                curColumns.add(curColIndex, col);
            } else {
                col = (List)curColumns.get(curColIndex);
            }
            RelationType type = ((Expr)varExpr_.getParent()).getType();
            col.add(
                ((SigType)type.getBasicTypeAt(0)).getSignature().getName().getId().nodeString());
                */
        }

        public void visit(BinaryExpr binExpr_) {
            BinaryExprOp op = binExpr_.getOp();
            
            if (op.equals(BinaryExprOp.UNION) || op.equals(BinaryExprOp.INTERSECT)) {
                binExpr_.getLeft().applyVisitor(this);
                binExpr_.getRight().applyVisitor(this);
            }
            else if (op.equals(BinaryExprOp.DIFF)) {
                binExpr_.getLeft().applyVisitor(this);
            }
            else if (op.equals(BinaryExprOp.JOIN_DOT) || op.equals(BinaryExprOp.JOIN_DOUBLECOLON)) {
                List col;
                if (curColumns.size() <= curColIndex) {
                    col = new ArrayList();
                    curColumns.add(curColIndex, col);
                } else {
                    col = (List)curColumns.get(curColIndex);
                }
                RelationType type = (RelationType)binExpr_.getType().getTypeIter().next();
                col.add(((SigType)type.getBasicTypeAt(0)).getSignature().getName().getId().nodeString());
            }
        }

        public void visit(Signature signature_) {
            curSig = signature_;

            //  get the name of the sig
            curSigName = signature_.getName().getId().nodeString();

            // create the AlloyType for this sig
            AlloyType curType = new AlloyType(curSigName);
            _ts.addType(curType);
            //System.out.println("new type--" + curName);

            // create an atom for this sig if it doesn't already exist
            if (_sigNamesToAtoms.get(curSigName) == null) {
                AlloyAtom atom = new AlloyAtom(curSigName, curType, 0);
                _sigNamesToAtoms.put(curSigName, atom);
            }

            // check if it's a subset
            Iterator subsetExprs = signature_.getSubsets().getSigExprIter();
            if (subsetExprs.hasNext()) {
                _ts.addSuper(_set, curType);

                //  record the "in" tuples
                while (subsetExprs.hasNext()) {
                    SigExpr currentExpr = (SigExpr)subsetExprs.next();
                    String extendedName = currentExpr.getSig().getId().nodeString();
                    List nameTup = new LinkedList();
                    nameTup.add(curSigName);
                    nameTup.add(extendedName);
                    _inSet.add(nameTup);
                }

            } else {

                // must be a type
                _ts.addSuper(_type, curType);

                // now see if it's a subtype
                SigExprs ext = signature_.getExtends();
                Iterator exprs = ext.getSigExprIter();

                while (exprs.hasNext()) {
                    // extends something -- a subtype in alloy 3.0
                    SigExpr currentExpr = (SigExpr)exprs.next();
                    String extendedName = currentExpr.getSig().getId().nodeString();
                    List nameTup = new LinkedList();
                    nameTup.add(curSigName);
                    nameTup.add(extendedName);
                    _extendsSet.add(nameTup);
                }
            }

            // FIELDS

            Iterator decls = signature_.getDecls().getDeclIter();
            Decl decl;

            while (decls.hasNext()) {
                decl = (Decl)decls.next();
                if (!decl.isEmpty()) {

                    curSetOfCols = new HashSet();

                    Iterator varIter = decl.getVariables().getVariableIter();
                    List curFieldNames = new ArrayList();
                    while (varIter.hasNext()) {
                        Variable curVar = (Variable)varIter.next();
                        String name = curVar.getId().nodeString();
                        curFieldNames.add(name);

                        // columns: List(first col sig name)->List(field name)->List(second col sig names)...                        
                    }

                    MultiplicityExpr declExpr = decl.getExpr();
                    if (declExpr instanceof ArrowMultExpr) {
                        processArrowMultExpr(curFieldNames, (ArrowMultExpr)declExpr);
                    } else {
                        processSetMultExpr(curFieldNames, (SetMultExpr)declExpr);
                    }
		    //System.out.println("curFieldNames  "+ curFieldNames);
                }

		//System.out.println("curSetOfCols " + curSetOfCols);

                // the arrows for fields are binary rels of type sig->sig
                List fieldRelTypes = new ArrayList();
                fieldRelTypes.add(_sig);
                fieldRelTypes.add(_sig);

                for (Iterator colsIter = curSetOfCols.iterator(); colsIter.hasNext();) {
                    List cols = (List)colsIter.next();
                    //                  generate all combinations from columns

                    int[] indices = new int[cols.size()];
                    // initialize indices
                    for (int i = 0; i < indices.length; i++) {
                        indices[i] = 0;
                    }

                    do {
                        List fieldTuple = new ArrayList();

                        String relName = (String) ((List)cols.get(0)).get(indices[0]);

                        if (indices.length > 3) {
                            // higher-arity relation.  attach the first type's name to the relation name
                            // and add in any types in the middle, too
                            // so a relation r from A->B->C->D appears as an edge (AlloyRelation) A.r[B]
                            // from C to D
                            relName = ((String) ((List)cols.get(1)).get(indices[1])) + "." + relName;

                            for (int i = 2; i < indices.length - 2; i++) {
                                relName = relName + "[" + (String) ((List)cols.get(i)).get(indices[i]) + "]";
                            }
                        } else {
                            // do nothing
                            // relation is binary (3 includes field name)
                            // simply use the field's name for the relation name (foo as opposed to A.foo)
                        }

                        int penultimate = indices.length - 2;
                        int last = indices.length - 1;
                        fieldTuple.add(((List)cols.get(penultimate)).get(indices[penultimate]));
                        fieldTuple.add(((List)cols.get(last)).get(indices[last]));

                        AlloyRelation fieldRel = new AlloyRelation(relName, fieldRelTypes);

                        Set tupleSet = (Set)_fieldNameToTuples.get(fieldRel);
                        if (tupleSet == null) {
                            tupleSet = new HashSet();
                            _fieldNameToTuples.put(fieldRel, tupleSet);
                        }
                        tupleSet.add(fieldTuple);
			//System.out.println("Tuple set for " + fieldRel + " is " + tupleSet);

                    } while (increment(indices, cols, indices.length - 1));

                    //generateFieldRel(cols.size());

                }

            }
        }

        private void processArrowMultExpr(List varNames, ArrowMultExpr arrowMultExpr) {
            //System.out.println("********processArrowMult******* " + arrowMultExpr);

            // create a new columns list for the arrow expr
            curColumns = new ArrayList();
            curSetOfCols.add(curColumns);
            curColumns.add(varNames);
            List curSigNameList = new ArrayList();
            curSigNameList.add(curSigName);
            curColumns.add(curSigNameList);

            processArrowExpr(arrowMultExpr.toExpr());
        }

        private void processSetMultExpr(List varNames, SetMultExpr setMultExpr) {
            //System.out.println("********processSetMult******* " + setMultExpr);
            curColIndex = 2;
            
            Expr expr = setMultExpr.toExpr();
            if (expr instanceof BinaryExpr) {
                if (((BinaryExpr)expr).getOp().equals(BinaryExprOp.RELATION)) {
                    // A->B->C
                    //multiplyOutCurCols = true;
                    curColumns = new ArrayList();
                    curSetOfCols.add(curColumns);
                    curColumns.add(varNames);
                    List curSigNameList = new ArrayList();
                    curSigNameList.add(curSigName);
                    curColumns.add(curSigNameList);
                    processArrowExpr(expr);
                } else {
                    // for some odd reason, these expr's are parsed this way:
                    // (A+B)+C instead of A+(B+C)
                    while (expr instanceof BinaryExpr
                        && ((BinaryExpr)expr).getOp().equals(BinaryExprOp.UNION)) {

                        BinaryExpr binExpr = (BinaryExpr)expr;

                        //System.out.println("  " + binExpr.getRight());

                        curColumns = new ArrayList();
                        curSetOfCols.add(curColumns);
                        curColumns.add(varNames);
                        List curSigNameList = new ArrayList();
                        curSigNameList.add(curSigName);
                        curColumns.add(curSigNameList);

                        processArrowExpr(binExpr.getRight());
                        expr = binExpr.getLeft();

                    }
                    // process last col
                    curColumns = new ArrayList();
                    curSetOfCols.add(curColumns);
                    curColumns.add(varNames);
                    List curSigNameList = new ArrayList();
                    curSigNameList.add(curSigName);
                    curColumns.add(curSigNameList);

                    processArrowExpr(expr);
                }
            } else {
                // single term, process it
                //multiplyOutCurCols = false;
                curColumns = new ArrayList();
                curSetOfCols.add(curColumns);
                curColumns.add(varNames);
                List curSigNameList = new ArrayList();
                curSigNameList.add(curSigName);
                curColumns.add(curSigNameList);
                
                //System.out.println("  " + expr);
                expr.applyVisitor(this);
            }

        }

        /**
         * processes an expr separated by arrows
         * @param expr
         */
        private void processArrowExpr(Expr expr) {
            //System.out.println("processArrowExpr: " + expr);
            if (!(expr instanceof BinaryExpr
                && ((BinaryExpr)expr).getOp().equals(BinaryExprOp.RELATION))) {
                expr.applyVisitor(this);
                return;
            }

            curColIndex = 2;

            do {
                processSumExpr(((BinaryExpr)expr).getLeft());
                expr = ((BinaryExpr)expr).getRight();
                curColIndex++;
            } while (
                expr instanceof BinaryExpr
                    && ((BinaryExpr)expr).getOp().equals(BinaryExprOp.RELATION));
            processSumExpr(expr);

        }

        /**
         * processes an expr separated by +, -
         *
         */
        private void processSumExpr(Expr expr) {
            //System.out.println("process sum expr: " + expr);
            if (!(expr instanceof BinaryExpr)) {
                //System.out.println("  " + expr);
                processArrowExpr(expr);
                return;
            }

            boolean normalExit = true;

            while (expr instanceof BinaryExpr) {
                BinaryExpr binExpr = (BinaryExpr)expr;
                if (binExpr.getOp().equals(BinaryExprOp.UNION)) {
                    //System.out.println("  " + binExpr.getLeft());
                    processArrowExpr(binExpr.getLeft());
                    expr = binExpr.getRight();
                } else if (binExpr.getOp().equals(BinaryExprOp.DIFF)) {
                    // don't show B in the case of foo: A-B
                    //System.out.println("  " + binExpr.getLeft());
                    processArrowExpr(binExpr.getLeft());
                    normalExit = false;
                    break;
                } else {
                    // something of the form A+B+C&D, look at the C&D as one piece
                    break;
                }
            }
            if (normalExit) {
                //System.out.println("  " + expr);
                processArrowExpr(expr);
            }

        }

        private boolean increment(int[] indices, List columns, int changeAt) {
            if (indices[changeAt] == ((List)columns.get(changeAt)).size() - 1) {
                indices[changeAt] = 0;
                if (changeAt == 0) {
                    return false;
                } else {
                    return increment(indices, columns, changeAt - 1);
                }
            } else {
                indices[changeAt] += 1;
                return true;
            }
        }
    }

}
